"""
插件加载器

负责从不同来源加载插件。
"""

import os
import sys
import importlib
import importlib.util
from typing import List, Dict, Any, Optional, Type, Union
from pathlib import Path
from abc import ABC, abstractmethod
import yaml
import json

from .base import Plugin, PluginMetadata, PluginState
from .exceptions import PluginLoadError, PluginNotFoundError


class PluginLoader(ABC):
    """插件加载器抽象基类"""
    
    @abstractmethod
    def load_plugin(self, source: str) -> Plugin:
        """
        加载插件
        
        Args:
            source: 插件源（路径、模块名等）
        
        Returns:
            插件实例
        
        Raises:
            PluginLoadError: 加载失败
        """
        pass
    
    @abstractmethod
    def discover_plugins(self, source: str) -> List[str]:
        """
        发现插件
        
        Args:
            source: 搜索源
        
        Returns:
            插件源列表
        """
        pass
    
    def load_metadata(self, source: str) -> Optional[PluginMetadata]:
        """
        加载插件元数据
        
        Args:
            source: 插件源
        
        Returns:
            插件元数据，如果没有则返回None
        """
        return None


class DirectoryPluginLoader(PluginLoader):
    """
    目录插件加载器
    
    从指定目录加载插件。
    """
    
    def __init__(self, plugin_dir: str = "./plugins"):
        self.plugin_dir = Path(plugin_dir)
        self.plugin_dir.mkdir(parents=True, exist_ok=True)
    
    def discover_plugins(self, source: str = None) -> List[str]:
        """发现目录中的插件"""
        if source:
            search_dir = Path(source)
        else:
            search_dir = self.plugin_dir
        
        if not search_dir.exists():
            return []
        
        plugins = []
        
        # 搜索Python文件
        for py_file in search_dir.glob("**/*.py"):
            if py_file.name.startswith("__"):
                continue
            plugins.append(str(py_file))
        
        # 搜索插件目录（包含__init__.py的目录）
        for plugin_dir in search_dir.iterdir():
            if plugin_dir.is_dir() and (plugin_dir / "__init__.py").exists():
                plugins.append(str(plugin_dir))
        
        return plugins
    
    def load_plugin(self, source: str) -> Plugin:
        """从文件或目录加载插件"""
        source_path = Path(source)
        
        if not source_path.exists():
            raise PluginNotFoundError(f"插件源不存在: {source}")
        
        try:
            if source_path.is_file():
                return self._load_from_file(source_path)
            elif source_path.is_dir():
                return self._load_from_directory(source_path)
            else:
                raise PluginLoadError(f"不支持的插件源类型: {source}")
        
        except Exception as e:
            if isinstance(e, (PluginLoadError, PluginNotFoundError)):
                raise
            raise PluginLoadError(f"加载插件失败: {e}", plugin_path=source, cause=e)
    
    def _load_from_file(self, file_path: Path) -> Plugin:
        """从Python文件加载插件"""
        module_name = file_path.stem
        
        # 加载模块
        spec = importlib.util.spec_from_file_location(module_name, file_path)
        if spec is None or spec.loader is None:
            raise PluginLoadError(f"无法创建模块规范: {file_path}")
        
        module = importlib.util.module_from_spec(spec)
        
        # 添加到sys.modules以支持相对导入
        sys.modules[module_name] = module
        
        try:
            spec.loader.exec_module(module)
        except Exception as e:
            # 清理sys.modules
            if module_name in sys.modules:
                del sys.modules[module_name]
            raise PluginLoadError(f"执行模块失败: {e}", cause=e)
        
        # 查找插件类
        plugin_class = self._find_plugin_class(module)
        if plugin_class is None:
            raise PluginLoadError(f"在模块中未找到插件类: {file_path}")
        
        # 创建插件实例
        plugin = plugin_class()
        
        # 加载元数据
        metadata = self._load_metadata_from_module(module, file_path.parent)
        if metadata:
            plugin.metadata = metadata
        
        return plugin
    
    def _load_from_directory(self, dir_path: Path) -> Plugin:
        """从目录加载插件"""
        init_file = dir_path / "__init__.py"
        if not init_file.exists():
            raise PluginLoadError(f"插件目录缺少__init__.py: {dir_path}")
        
        module_name = dir_path.name
        
        # 添加父目录到sys.path
        parent_dir = str(dir_path.parent)
        if parent_dir not in sys.path:
            sys.path.insert(0, parent_dir)
            path_added = True
        else:
            path_added = False
        
        try:
            # 导入模块
            module = importlib.import_module(module_name)
            
            # 查找插件类
            plugin_class = self._find_plugin_class(module)
            if plugin_class is None:
                raise PluginLoadError(f"在模块中未找到插件类: {dir_path}")
            
            # 创建插件实例
            plugin = plugin_class()
            
            # 加载元数据
            metadata = self._load_metadata_from_module(module, dir_path)
            if metadata:
                plugin.metadata = metadata
            
            return plugin
        
        finally:
            # 清理sys.path
            if path_added and parent_dir in sys.path:
                sys.path.remove(parent_dir)
    
    def _find_plugin_class(self, module) -> Optional[Type[Plugin]]:
        """在模块中查找插件类"""
        # 检查是否有PLUGIN_CLASS属性
        if hasattr(module, 'PLUGIN_CLASS'):
            plugin_class = getattr(module, 'PLUGIN_CLASS')
            if isinstance(plugin_class, type) and issubclass(plugin_class, Plugin):
                return plugin_class
        
        # 搜索所有Plugin子类
        plugin_classes = []
        for attr_name in dir(module):
            attr = getattr(module, attr_name)
            if (isinstance(attr, type) and 
                issubclass(attr, Plugin) and 
                attr != Plugin):
                plugin_classes.append(attr)
        
        if len(plugin_classes) == 1:
            return plugin_classes[0]
        elif len(plugin_classes) > 1:
            # 多个插件类，尝试找到主插件类
            for cls in plugin_classes:
                if cls.__name__.endswith('Plugin'):
                    return cls
            # 返回第一个
            return plugin_classes[0]
        
        return None
    
    def _load_metadata_from_module(self, module, plugin_dir: Path) -> Optional[PluginMetadata]:
        """从模块加载元数据"""
        metadata = None
        
        # 1. 检查模块属性
        if hasattr(module, 'PLUGIN_METADATA'):
            metadata_dict = getattr(module, 'PLUGIN_METADATA')
            if isinstance(metadata_dict, dict):
                metadata = PluginMetadata.from_dict(metadata_dict)
        
        # 2. 检查plugin.yaml文件
        if metadata is None:
            yaml_file = plugin_dir / "plugin.yaml"
            if yaml_file.exists():
                metadata = self._load_metadata_from_yaml(yaml_file)
        
        # 3. 检查plugin.json文件
        if metadata is None:
            json_file = plugin_dir / "plugin.json"
            if json_file.exists():
                metadata = self._load_metadata_from_json(json_file)
        
        return metadata
    
    def _load_metadata_from_yaml(self, yaml_file: Path) -> Optional[PluginMetadata]:
        """从YAML文件加载元数据"""
        try:
            with open(yaml_file, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f)
                if data:
                    return PluginMetadata.from_dict(data)
        except Exception as e:
            raise PluginLoadError(f"加载YAML元数据失败: {e}", plugin_path=str(yaml_file), cause=e)
        return None
    
    def _load_metadata_from_json(self, json_file: Path) -> Optional[PluginMetadata]:
        """从JSON文件加载元数据"""
        try:
            with open(json_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
                if data:
                    return PluginMetadata.from_dict(data)
        except Exception as e:
            raise PluginLoadError(f"加载JSON元数据失败: {e}", plugin_path=str(json_file), cause=e)
        return None
    
    def load_metadata(self, source: str) -> Optional[PluginMetadata]:
        """加载插件元数据（不加载插件本身）"""
        source_path = Path(source)
        
        if source_path.is_file():
            return self._load_metadata_from_yaml(source_path.parent / "plugin.yaml")
        elif source_path.is_dir():
            return self._load_metadata_from_yaml(source_path / "plugin.yaml")
        
        return None


class PackagePluginLoader(PluginLoader):
    """
    包插件加载器
    
    从Python包加载插件。
    """
    
    def discover_plugins(self, source: str) -> List[str]:
        """发现包中的插件"""
        try:
            package = importlib.import_module(source)
            plugins = []
            
            # 检查包的__all__属性
            if hasattr(package, '__all__'):
                for name in package.__all__:
                    if name.endswith('Plugin'):
                        plugins.append(f"{source}.{name}")
            else:
                # 搜索包中的所有模块
                if hasattr(package, '__path__'):
                    import pkgutil
                    for importer, modname, ispkg in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
                        plugins.append(modname)
            
            return plugins
        
        except ImportError:
            return []
    
    def load_plugin(self, source: str) -> Plugin:
        """从包加载插件"""
        try:
            # 尝试直接导入为插件类
            if '.' in source:
                module_name, class_name = source.rsplit('.', 1)
                module = importlib.import_module(module_name)
                
                if hasattr(module, class_name):
                    plugin_class = getattr(module, class_name)
                    if isinstance(plugin_class, type) and issubclass(plugin_class, Plugin):
                        return plugin_class()
            
            # 尝试导入为模块
            module = importlib.import_module(source)
            
            # 查找插件类
            plugin_class = self._find_plugin_class(module)
            if plugin_class is None:
                raise PluginLoadError(f"在包中未找到插件类: {source}")
            
            return plugin_class()
        
        except ImportError as e:
            raise PluginNotFoundError(f"无法导入插件包: {source}", cause=e)
        except Exception as e:
            raise PluginLoadError(f"加载插件包失败: {e}", cause=e)
    
    def _find_plugin_class(self, module) -> Optional[Type[Plugin]]:
        """在模块中查找插件类"""
        # 与DirectoryPluginLoader相同的逻辑
        if hasattr(module, 'PLUGIN_CLASS'):
            plugin_class = getattr(module, 'PLUGIN_CLASS')
            if isinstance(plugin_class, type) and issubclass(plugin_class, Plugin):
                return plugin_class
        
        plugin_classes = []
        for attr_name in dir(module):
            attr = getattr(module, attr_name)
            if (isinstance(attr, type) and 
                issubclass(attr, Plugin) and 
                attr != Plugin):
                plugin_classes.append(attr)
        
        if len(plugin_classes) == 1:
            return plugin_classes[0]
        elif len(plugin_classes) > 1:
            for cls in plugin_classes:
                if cls.__name__.endswith('Plugin'):
                    return cls
            return plugin_classes[0]
        
        return None


class CompositePluginLoader(PluginLoader):
    """
    复合插件加载器
    
    组合多个插件加载器。
    """
    
    def __init__(self, loaders: List[PluginLoader]):
        self.loaders = loaders
    
    def discover_plugins(self, source: str) -> List[str]:
        """使用所有加载器发现插件"""
        all_plugins = []
        for loader in self.loaders:
            try:
                plugins = loader.discover_plugins(source)
                all_plugins.extend(plugins)
            except Exception:
                # 忽略单个加载器的错误
                continue
        return all_plugins
    
    def load_plugin(self, source: str) -> Plugin:
        """尝试使用所有加载器加载插件"""
        last_error = None
        
        for loader in self.loaders:
            try:
                return loader.load_plugin(source)
            except Exception as e:
                last_error = e
                continue
        
        # 所有加载器都失败
        if last_error:
            raise last_error
        else:
            raise PluginLoadError(f"所有加载器都无法加载插件: {source}")
    
    def load_metadata(self, source: str) -> Optional[PluginMetadata]:
        """尝试使用所有加载器加载元数据"""
        for loader in self.loaders:
            try:
                metadata = loader.load_metadata(source)
                if metadata:
                    return metadata
            except Exception:
                continue
        return None


# 默认插件加载器
default_loader = CompositePluginLoader([
    DirectoryPluginLoader(),
    PackagePluginLoader()
])
